/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

int sched_init(struct node_fs *this);
int sched_deinit(struct node_fs *this);
int sched_task_notify(struct node_fs *this, struct node_fs *link);
int sched_switch_to(struct node_fs *this, void *buf, u32 offset, u32 count);
int sched_schedule(struct node_fs *this, void *buf, u32 offset, u32 count);

#define GET_TASK(fs) ((struct task *)(fs)->class)

//The main task registry
struct node_fs sched = {
	.type = TYPE_FS,
	.name = "sched",
	.init = sched_init,
	.deinit = sched_deinit,
	.read = dummy,
	.write = sched_schedule,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_LIST_INIT,
};

//The task that is running currently. The task that refers to this always accesses its own task structure.
struct task *current;

struct node_fs sched_current_ptr = {
	.type = TYPE_FS,
	.name = "current_ptr",
	.class = &current,
	NODE_FS_INIT_VARS,
	NODE_FS_INIT_INIT,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
};
struct node_fs tasks = {
	.type = TYPE_FS,
	.name = "tasks",
	NODE_FS_INIT_INIT,
	.read = dummy,
	.write = sched_switch_to,
	.enumerate = dummy,
	.notify = sched_task_notify,
	NODE_FS_INIT_VARS,
	NODE_FS_LIST_INIT,
};

struct node_fs waiting = {
	.type = TYPE_FS,
	.name = "waiting",
	NODE_FS_INIT_VARS,
	NODE_FS_INIT_INIT,
	NODE_FS_OPS_INIT,
	NODE_FS_LIST_INIT,
};



/*Called after a task returns*/
void sched_end_me()
{
	printk("\nEnding %s\n", current->fs->name);
	fs_unlink("/sched/tasks", current->fs);
	fs_write("/sched", NULL, 0, 0); //schedule! and we are dead
	printk("Damn! %s didn't end!\n", current->fs->name);
}


void *sched_get_task_data(struct node_fs *task_fs)
{
	struct task *task;
	task = GET_TASK(task_fs);
	return task->task_data;
}

void sched_set_task_data(struct node_fs *task_fs, void *task_data)
{
	struct task *task;
	task = GET_TASK(task_fs);
	task->task_data = task_data;
}


int sched_switch_to(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	struct task *task, *current_backup;
	task = GET_TASK((struct node_fs *)buf);
	current_backup = current;
	current = task;
	if(task->first_run) {
		task->first_run = 0;
		arch_switch_task_first(task->stack_ptr, &current_backup->stack_ptr);
	} else {
		arch_switch_task(task->stack_ptr, &current_backup->stack_ptr);
	}
	return 0;
}


int sched_schedule(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	struct node_fs *last;
	struct task *last_task;
	last = current->fs;
	last_task = GET_TASK(last);
	if(current->fs->next)
		current = GET_TASK(current->fs->next);
	else {
		if(tasks.links->next) {
			current = GET_TASK(tasks.links->next);
		} else {
			current = GET_TASK(tasks.links);
		}
	}
	if(current != last_task) {
		if(current->first_run) {
			current->first_run = 0;
			arch_switch_task_first(current->stack_ptr, &last_task->stack_ptr);
		} else {
			arch_switch_task(current->stack_ptr, &last_task->stack_ptr);
		}
	}
}


extern int main(struct node_fs *this);

struct node_fs idle_fs;
int init_idle(struct node_fs *this)
{
	int i;
	struct node_fs *main_task;
	current = GET_TASK(&idle_fs);
	main_task = fs_create("main");
	main_task->init = main;
	main_task->class = malloc(sizeof(struct task));
	main_task->type = TYPE_FS;
	fs_link("/sched/tasks", main_task);
	fs_write("/sched", NULL, 0, 0);
	while(1) {
		service_events(0);
		arch_halt();
	}
}

struct task idle;
struct node_fs idle_fs = {
	.type = TYPE_FS, .name = "idle",
	NODE_FS_OPS_INIT,
	.init = init_idle, .deinit = dummy,
	.class = &idle,
	NODE_FS_INIT_VARS,
	NODE_FS_LIST_INIT,
};





/*This function allocates a 4kB stack for a process. The pointer is set at the end of the stack with an extra STACK_SAFE space beyond it*/
static int stack_malloc(void **stack_ptr)
{
	*(stack_ptr) = malloc(0x4100);
	if(!(*stack_ptr))
		return EMEMERR;
	(*(stack_ptr))+=0x4000;
	return 0;
}
/*This is called when a task registers in the /sched directory*/
int sched_task_notify(struct node_fs *this, struct node_fs *link)
{
	struct task *new_task;
	new_task = (struct task *)link->class; //Get the pointer to the task from the registered node
	if(new_task->up == 0) {
		stack_malloc(&new_task->stack);
		new_task->stack_ptr = new_task->stack;
		new_task->stack_ptr = arch_prepare_task(new_task->stack_ptr, link, link->init); //Prepare a proper stack pointer for the task
		new_task->fs = link;
		new_task->first_run = 1;
		new_task->up = 1;
		new_task->editing = NULL;
		new_task->editing_node = NULL;
		printk("%s initialized, stack = %x\n", link->name, GET_TASK(link)->stack);
	}
	return 0;
}


/*This function which should not be here edits the current application being edited by this task*/
int set_editing(char *path)
{
	current->editing = fs_get(path);
	return 0;
}

int set_editing_noparse(struct node_fs *n)
{
	current->editing = n;
	return 0;
}

struct node_fs *get_editing()
{
	return current->editing;
}

int set_editing_node(struct node *node)
{
	current->editing_node = node;
	return 0;
}

struct node *get_editing_node()
{
	return current->editing_node;
}


int sched_init(struct node_fs *this)
{
	//Start the task notifier in order to initiate tasks that register with us
	fs_link("/sched", &tasks);
	fs_link("/sched", &waiting);
	//Start up the idle task and let it do the remaining intiation.
	fs_link("/sched/tasks", &idle_fs);
	//Now the idle task has been initiated by sched_task_notify and now, we should simply dispatch it.
	GET_TASK(&idle_fs)->first_run = 0; //This task is manually started and no context is saved
	arch_start_task(GET_TASK(&idle_fs)->stack_ptr);
	while(1);
}

int sched_deinit(struct node_fs *this)
{
	return 0;
}


int init_sched()
{
	fs_link("/", &sched);
	sched_init(&sched);
	return 0;
}
